"""
Copyright © 2022 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/micropython-timer-dispatcher
"""
__version__ = '1.1'
__version_info__ = (1, 1)
print('module dispatcher version:', __version__)

from machine import Timer


class Worker(object):
    '''定时器任务'''

    def __init__(self, work: callable, period: int, *params):
        '''
        初始化定时器任务

        参数：
        - work：任务函数
        - period：任务执行间隔，单位 毫秒
        - params：任务函数参数列表
        '''
        self.__counter = 0
        self.__work = work
        self.__period = period
        self.__params = params

    @property
    def counter(self):
        return self.__counter

    @counter.setter
    def counter(self, count):
        self.__counter = count

    @property
    def period(self):
        return self.__period

    def do_work(self):
        '''执行任务函数'''
        try:
            self.__work(*self.__params)
        except Exception as e:
            print(f"Error executing task: {e}")


class Dispatcher(object):
    '''定时器任务调度器'''
    __DEFAULT_PERIOD = 20

    def __init__(self, timer_id=0):
        '''
        初始化任务调度器

        参数：
        - timer_id：定时器 ID，默认值 0。
        '''
        if not isinstance(timer_id, int):
            timer_id = 0

        self.__workers = {}
        self.__timer = Timer(timer_id)
        self.__paused = False

        self.__timer.init(mode=Timer.PERIODIC, period=Dispatcher.__DEFAULT_PERIOD, callback=self.__worker_callback)

    def deinit(self):
        '''反初始化任务调度器，清理资源'''
        self.__timer.deinit()
        self.__workers = {}

    def __worker_callback(self, _):
        '''定时器回调函数，用于执行任务'''
        if self.__paused:
            return

        for worker in list(self.__workers.values()):
            if self.__paused:
                break

            worker.counter += 1

            if (worker.counter * Dispatcher.__DEFAULT_PERIOD) % worker.period == 0:
                worker.counter = 0
                worker.do_work()

    def add_work(self, work: callable, period: int, *params) -> bool:
        '''
        添加/更新一个调度任务

        参数：
        - work：任务函数
        - period：任务执行间隔，单位 毫秒
        - params：任务函数参数列表
        '''
        try:
            if callable(work):
                self.__workers[id(work)] = Worker(work, period, *params)
                return True
            else:
                print('work must be a function')
                return False
        except Exception as e:
            print(f"Error adding work: {e}")
            return False

    def has_work(self, work: callable) -> bool:
        '''
        判断任务是否在队列中

        参数：
        - work：任务函数
        '''
        return callable(work) and id(work) in self.__workers

    def del_work(self, work: callable = None):
        '''
        删除指定或最后添加的任务

        参数：
        - work：任务函数，默认值 None
        '''
        try:
            if work is None:
                if self.__workers:
                    self.pause()
                    self.__workers.popitem()
            elif callable(work) and self.has_work(work):
                self.pause()
                self.__workers.pop(id(work))
        except Exception as e:
            print(f"Error deleting work: {e}")

    def del_works(self):
        '''删除所有任务'''
        try:
            self.pause()
            self.__workers.clear()
        except Exception as e:
            print(f"Error clearing works: {e}")

    def pause(self):
        '''暂停/开启 所有任务'''
        self.__paused = not self.__paused

    def is_paused(self):
        '''判断所有任务是否正在运行'''
        return self.__paused


def run_test():
    '''运行测试代码'''
    from machine import RTC

    counter = 0

    class Test(object):
        def __init__(self):
            self.__counter = 0

        def task3(self):
            self.__counter += 1
            print(f'\t\t\ttask 3 counter: {self.__counter}')

    def task1():
        nonlocal counter
        counter = (counter + 1) % 3

        if counter == 0:
            print('')
            if tasks.has_work(task2):
                tasks.del_work(task2)
            else:
                tasks.add_work(task2, 3000, 'task', 2)

        print(f'\x1b[32m{rtc.datetime()[4:7]} task 1 (5s)\033[0m')

    def task2(prefix: str, index: int):
        print(f'\x1b[33m{rtc.datetime()[4:7]} {prefix} {index} (3s)\033[0m')

    rtc = RTC()
    rtc.init((2000, 1, 1, 0, 0, 0, 0, 8))

    test = Test()
    task3 = lambda: test.task3()

    tasks = Dispatcher()
    tasks.add_work(task1, 5000)
    tasks.add_work(task3, 1000)


if __name__ == '__main__':
    run_test()
